#!/usr/bin/env python
# -*- coding: utf-8 -*-
#=======================================#
# DarkDev
# @author: RedSoxFan and NerfSoldier
# @copyright: 2012 RedSoxFan and NerfSoldier
# @license: GNU General Public License v3.0 
#           <http://www.gnu.org/copyleft/gpl.html>
# @requires: Qt by Nokia
#            PyQt4 by Riverbank Computing Limited
#            Python 2.6 or 2.7
# @status: Development
# @summary: An IDE with support to create
#           language plug-ins for any
#           language desired
# @version: 0.2-devel-120716-0
#=======================================#

#=======================================#
# Imports
#=======================================#
# Python
import os
import mimetypes
from PyQt4 import QtGui, QtCore
import re
import sys
sys.dont_write_bytecode = True

# Verify Path Before Loading DarkDev Modules
FILE = os.path.abspath(sys.argv[0])
FOLDER = os.path.dirname(FILE)
if FOLDER not in sys.path: sys.path.append(FOLDER)
os.chdir(FOLDER)

# DarkDev
from ddlogger import Logger
from parsers import Parser as PlainText
from parsers.ddcg import DDCGObject
from templates import Template
from workspace import Workspace
#=======================================#

class DarkDev(QtGui.QMainWindow):
    APPNAME  = "DarkDev"                    # Application Name
    AUTHORS  = "RedSoxFan and NerfSoldier"  # Authors
    EXE      = FILE.endswith('.exe')        # Running from EXE?
    FILE     = FILE                         # Application File
    FOLDER   = FOLDER                       # Application Folder 
    ICON     = "resources/icon/icon.png"    # Icon File Name - Changed To QIcon On Initialization
    ICON_PROVIDER = None                    # QFileIconProvider - Set On Initialization
    INSTANCE = None                         # Static Reference To DarkDev Instance - Set On Initialization
    PROCNAME = "darkdev"                    # Requested Process Name
    VERSION  = "0.2-devel-120716-0"         # Version String
    
    DEVEL = not(EXE or 'devel' not in VERSION) # Running in a development environment?
    
    def __init__(self,sp):
        super(DarkDev,self).__init__()
        self.splash = sp
        if not DarkDev.INSTANCE: DarkDev.INSTANCE = self
        
    def init(self):
        # Initialize DarkDev
        self.logger = Logger(DarkDev.DEVEL and Logger.LEVEL_DEBUG or Logger.LEVEL_ERROR)
        DarkDev.ICON = QtGui.QIcon(DarkDev.ICON)
        DarkDev.ICON_PROVIDER = QtGui.QFileIconProvider()
        self.load_ddcg()
        self.load_parsers()
        self.load_templates()
        
        # Initialize GUI
        self.splash.setFirstLine("Initializing Window")
        self.setWindowState(QtCore.Qt.WindowMaximized)
        self.setWindowTitle(DarkDev.APPNAME)
        self.setWindowIcon(DarkDev.ICON)
        
        # Initialize Central Widget
        self.mdi = QtGui.QMdiArea()
        #self.mdi.setViewMode(QtGui.QMdiArea.TabbedView)
        self.setCentralWidget(self.mdi)
        
        # Create MenuBar
        self.menubar = self.menuBar()
        
        # MenuBar > File
        self.menu_file = self.menubar.addMenu('File')
        
        # MenuBar > File > New Workspace
        self.menu_file_new = QtGui.QAction(QtGui.QIcon('resources/workspace/new.png'), 'New Workspace', self)        
        self.menu_file_new.setShortcut('Ctrl+Shift+N')
        self.menu_file_new.setStatusTip('New Workspace')
        self.menu_file_new.triggered.connect(self.create_new_workspace)
        self.menu_file.addAction(self.menu_file_new)
        
        # MenuBar > File > Open Workspace
        self.menu_file_open = QtGui.QAction(QtGui.QIcon('resources/workspace/open.png'), 'Open Workspace', self)        
        self.menu_file_open.setShortcut('Ctrl+Shift+O')
        self.menu_file_open.setStatusTip('Open Workspace')
        self.menu_file_open.triggered.connect(self.open_workspace)
        self.menu_file.addAction(self.menu_file_open)
        
        self.menu_file.addSeparator()
        
        # MenuBar > File > Close Workspace
        self.menu_file_close = QtGui.QAction(QtGui.QIcon('resources/workspace/close.png'), 'Close Workspace', self)        
        self.menu_file_close.setShortcut('Ctrl+W')
        self.menu_file_close.setStatusTip('Close Workspace')
        self.menu_file_close.triggered.connect(self.mdi.closeActiveSubWindow)
        self.menu_file.addAction(self.menu_file_close)
        
        self.menu_file.addSeparator()
        
        # MenuBar > File > Exit DarkDev
        self.menu_file_exit = QtGui.QAction(QtGui.QIcon('resources/close.png'), 'Exit DarkDev', self)        
        self.menu_file_exit.setShortcut('Ctrl+Q')
        self.menu_file_exit.setStatusTip('Exit DarkDev')
        self.menu_file_exit.triggered.connect(self.close)
        self.menu_file.addAction(self.menu_file_exit)
        
        
        # MenuBar > Workspaces
        self.ws = [] # List to hold open workspaces
        self.menu_ws = self.menubar.addMenu('Workspaces')
        
        # MenuBar > Help
        self.menu_help = self.menubar.addMenu('About')
        
        # MenuBar > Help > About Qt
        self.menu_help_about_qt = QtGui.QAction(QtGui.QIcon('resources/qt.png'), 'About Qt', self)
        self.menu_help_about_qt.setStatusTip('About Qt')
        self.menu_help_about_qt.triggered.connect(QtGui.qApp.aboutQt)
        self.menu_help.addAction(self.menu_help_about_qt)
        
        # MenuBar > Help > About DarkDev 
        self.menu_help_about = QtGui.QAction(DarkDev.ICON, 'About %s'%DarkDev.APPNAME, self)        
        self.menu_help_about.setShortcut('Ctrl+F2')
        self.menu_help_about.setStatusTip('About %s'%DarkDev.APPNAME)
        self.menu_help_about.triggered.connect(self.about)
        self.menu_help.addAction(self.menu_help_about)
        
        # Restore Previously State
        self.splash.setFirstLine("Restoring Previous State")
        settings=QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, DarkDev.APPNAME)
        self.restoreGeometry(settings.value("geometry").toByteArray())
        self.restoreState(settings.value("windowState").toByteArray())
        for ws in str(settings.value("workspaces",QtCore.QString()).toString()).split(os.pathsep):
            if os.path.exists(ws): self.open_workspace(ws)
        for ws in self.ws:
            if ws.location==str(settings.value("focusedWorkspace").toString()):
                ws.window.focusNextChild()
                break
        self.show()
        
    def closeEvent(self,*a,**k):
        '''
        Save Application State (UserScope), Workspace States (WorkspaceScope - .ddws) and Exit
        '''
        # Save Application State (UserScope)
        settings=QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, DarkDev.APPNAME)
        settings.setValue("geometry", self.saveGeometry())
        settings.setValue("windowState", self.saveState())
        settings.setValue("workspaces",os.pathsep.join(x.location for x in self.ws))
        settings.setValue("focusedWorkspace",str(self.mdi.activeSubWindow().toolTip()))
        # Save Open Workspace States (WorkspaceScope - .ddws)
        while len(self.ws)>0: self.ws[0].clean()
        # Exit
        QtGui.qApp.quit()
    
    @staticmethod
    def clean():
        '''
        Clean Up
        '''
        # Deinitialize Parsers
        for parser in DarkDev.INSTANCE.parsers.values():
            try:
                parser.deinit(DarkDev.INSTANCE)
            except:
                DarkDev.INSTANCE.logger.warning("%s Failed To Deinitialize"%parser.description)
        DarkDev.INSTANCE.logger.debug("DarkDev Instance Was Successfully Cleaned")
    
    def create_new_workspace(self,*args,**kwargs):
        '''
        Create a new Workspace.
        All open workspaces are stored in the instance property 'ws'
        '''
        # Create New Workspace
        self.ws.append(Workspace.new_workspace(self))
        
    def open_workspace(self,location=None):
        '''
        Open Workspace
        @keyword location: Workspace Location (default=show file dialog)
        '''
        if not location:
            fd = QtGui.QFileDialog()
            fd.setFileMode(QtGui.QFileDialog.Directory)
            fd.setOption(QtGui.QFileDialog.ShowDirsOnly,True)
            location = fd.getExistingDirectory(self,'Select Workspace Location',os.path.expanduser("~"))
        if location:
            w=Workspace.open_workspace(self,location)
            self.ws.append(w)
            return w
        
    def load_ddcg(self):
        '''
        Try to locate and load the configuration from the DarkDev ConfiG (DDCG) file.
        The instance property 'ddcg' contains the loaded configuration or a blank one,
        if unable to locate the configuration (including the default one). 
        '''
        # Load DarkDev ConfiG
        self.ddcg = DDCGObject(self) # DDCG Database Object
        self.splash.setFirstLine("Searching For Configuration")
        for name in [os.path.join(os.path.expanduser("~"),".DarkDev","config.ddcg"),"config.ddcg","resources/default.ddcg"]:
            # Check to see if file 'name' exists and if it does load it and break
            self.splash.setSecondLine("Checking For %r"%os.path.abspath(name))
            if os.path.exists(name) and self.ddcg.load(name):
                self.splash.setFirstLine("Loading Configuration")
                self.logger.debug("Loaded DDCG From %r"%(os.path.abspath(name)))
                break
            
    def load_parsers(self):
        '''
        Try to load all parsers that are in the 'parsers' subfolder. 
        Any parser that can be imported and initialized correctly will 
        be added to the instance property 'parsers' which is a list
        '''
        # Verify that the PlainText parser can initialize correctly since it is the parser base
        self.splash.setFirstLine("Searching For Parsers")
        try:
            self.splash.setSecondLine("Initializing PlainText")
            if PlainText.init(self):
                self.parsers = {"PlainText":PlainText}
                self.logger.info("Found Parser - %s (%s)"%(PlainText.description,PlainText.filefilter))
            else:
                # FATAL ERROR: Parser Base Failed To Initialize
                self.logger.fatal("Failed To Initialize Parser Base - Exiting Now")
                raise SystemExit, "Fatal Error Occurred"
        except:
            # FATAL ERROR: Parser Base Failed To Initialize
            self.logger.fatal("Failed To Initialize Parser Base - Exiting Now")
            raise SystemExit, "Fatal Error Occurred"
        # Search for other parsers in the 'parsers' subfolder 
        # NOTE: Parsers MUST be python source files. Compiled PYC and PYO files are not acknowledged
        for parser in [x.replace(".py","") for x in os.listdir("parsers") if x.endswith(".py") and x!="__init__.py"]:
            try:
                # Attempt to do equivalent of 'from parsers import %s'%(parser)
                p = __import__("parsers.%s"%parser,fromlist=[parser])
                # Attempt to do equivalent of 'from parsers.%s import %s'%(parser,parsers.capitalize()'
                globals()[parser.capitalize()] = eval("p.%s"%parser.capitalize())
                # Delete reference to parser module. Only the parser class is required
                del p
                try:
                    # Try to initialize parser class
                    self.splash.setSecondLine("Initializing %s"%parser.capitalize())
                    if eval("%s.init(self)"%parser.capitalize()):
                        self.parsers[parser.capitalize()] = eval(parser.capitalize())
                        self.logger.info("Found Parser - %s (%s)"%(globals()[parser.capitalize()].description,globals()[parser.capitalize()].filefilter))
                    else:
                        self.logger.info("Parser %s Failed To Initialize (returned False)"%parser.capitalize())
                except:
                    self.logger.info("Parser %s Failed To Initialize"%parser.capitalize())
            except Exception, err:
                self.logger.info("%s Failed To Import - %r"%(parser.capitalize(),err))
                
    def load_templates(self):
        '''
        Try to load all the templates in the 'templates' subfolder.
        '''            
        self.templates = {'File':Template(self)}
        self.splash.setFirstLine("Loading Templates")
        self.logger.info("Found Template - %s"%(Template.name))
        for template in sorted([x[:-3] for x in os.listdir('templates') if x.endswith(".py") and not x.endswith('__init__.py')]):
            try:
                # Attempt to do equivalent of 'from templates import %s'%(template)
                p = __import__("templates.%s"%template,fromlist=[template])
                # Attempt to do equivalent of 'from templates.%s import %s'%(template,templates.capitalize()'
                globals()[template.capitalize()] = eval("p.%s(self)"%template.capitalize())
                # Delete reference to template module. Only the template class is required
                del p
                self.logger.info("Found Template - %s"%(globals()[template.capitalize()].name))
                self.templates[globals()[template.capitalize()].name] = globals()[template.capitalize()]
            except:
                self.logger.info("%s Failed To Import"%template.capitalize())
           
    def get_parser(self,pid):
        '''
        returns the parser class for parser that has the parser id of pid.
        On failure to find parser with the parser id of pid, the PlainText parser is returned instead. 
        '''
        return pid in self.parsers and self.parsers[pid] or PlainText

    def get_parser_for_file(self,fil):
        '''
        returns the parser class for parser that has the parser filter that matches fil. 
        '''
        for parser in [v for k,v in self.parsers.iteritems() if k!="PlainText"]:
            if re.match(parser.filefilter.replace(".","\.").replace("*",".*"),fil,re.I):
                return parser
        return PlainText
    
    def get_parser_for_file_with_name(self,fil):
        '''
        returns the parser class for and name of parser that has the parser filter that matches fil. 
        '''
        for name,parser in [(k,v) for k,v in self.parsers.iteritems() if k!="PlainText"]:
            if re.match(parser.filefilter.replace(".","\.").replace("*",".*"),fil,re.I):
                return (name,parser)
        return ("PlainText",PlainText)
    
    def can_open(self,fil):
        '''
        Return Whether DarkDev Can Open File
        '''
        mime = mimetypes.guess_type(fil)
        if mime[0] and mime[0].startswith('text/'): return True
        for parser in [v for k,v in self.parsers.iteritems() if k!="PlainText"]:
            if re.match(parser.filefilter.replace(".","\.").replace("*",".*"),fil,re.I):
                return True
        try:
            canread = open('resources/canopen.txt','r').read().split('\n')
            if fil[fil.rfind(".")+1:].lower() in canread: return True
            if fil in canread: return True
        except:
            pass
        return False
    
    def isimage(self,fil):
        '''
        Returns Whether File Is Image
        '''
        mime = mimetypes.guess_type(fil)
        if mime[0] and mime[0].startswith('image/'): return True
        return fil[fil.rfind(".")+1:].lower() in ["xcf","psd","svg"]
    
    def icon_for(self,fname):
        '''
        Returns System Icon For The File
        @param fname: Filename to get System Icon for
        '''
        return DarkDev.ICON_PROVIDER.icon(QtCore.QFileInfo(fname))
    
    def about(self,*args,**kwargs):
        '''
        Show About Dialog
        '''
        QtGui.QMessageBox.about(self,"About %s"%DarkDev.APPNAME,u"""
            <html>
                <body>
                    <h3 style="margin:0">About {APPNAME}</h3>
                    <h4 style="margin:0">Version {VERSION}</h4>
                    <h5 style="margin:0">\u00a92012 {AUTHORS}</h5>
                    <br>
                    <br>
                    {APPNAME} is an IDE with support to create language plug-ins for any language desired.<br>
                    <br>
                    {APPNAME} uses Qt by Nokia. <a href="http://qt.nokia.com">http://qt.nokia.com</a><br>
                    <br>
                    {APPNAME} uses PyQt4 by Riverbank Computing Limited. <a href="http://www.riverbankcomputing.co.uk/software/pyqt/intro">http://www.riverbankcomputing.co.uk/software/pyqt/intro</a><br>
                    <br>
                    {APPNAME} is programmed in Python. <a href="http://www.python.org">http://www.python.org</a><br>
                    {APPNAME} is currently running under Python {PYVER}<br>
                    <br>
                    {APPNAME} is licensed under the GNU General Public License v3.0<a href="http://www.gnu.org/copyleft/gpl.html">http://www.gnu.org/copyleft/gpl.html</a><br>
                    <center><img src="resources/gplv3.png" style="background:white"/></center><br>
                    <br>
                    The Windows Builds uses py2exe by Thomas Heller, Jimmy Retzlaff, and Mark Hammond <a href="http://www.py2exe.org/">http://www.py2exe.org/</a><br>
                    <br>
                    The Java Parser utilizes the JDK and JRE installed on this system.<br>
                    The location of the JDK is: <i><a href="{JDKPATH}">{JDKPATH}</a></i><br>
                    This vendor of the JRE is: <i>{JREVENDOR}</i> <a href="{JREVENDORURL}">{JREVENDORURL}</a><br>
                    This Java version is: <i>{JAVAVERSION}<i><br>
                    <br>
                    <i>Java, JRE and JDK are trademarks of Oracle<br>
                    Python is a trademark of The Python Foundation</i>
                </body>
            </html>""".format(APPNAME=DarkDev.APPNAME,
                              AUTHORS=DarkDev.AUTHORS,
                              VERSION=DarkDev.VERSION,
                              PYVER="%s.%s.%s"%(sys.version_info[0],sys.version_info[1],sys.version_info[2]),
                              JDKPATH=self.ddcg["Java.General.JDK"] or "&lt;Not Found&gt;",
                              JREVENDOR=self.ddcg["Java.General.JREVENDOR"] or "&lt;Not Found&gt;",
                              JREVENDORURL=self.ddcg["Java.General.JREVENDORURL"] or "",
                              JAVAVERSION=self.ddcg["Java.General.JAVAVERSION"] or "&lt;Not Found&gt;"))
        
if __name__=="__main__":
    class SplashScreen(QtGui.QSplashScreen):
        '''
        Custom Splash Screen That Has Support For 2 Lines Of Text
        '''
        def __init__(self,pixmap):
            QtGui.QSplashScreen.__init__(self,pixmap)
            f = self.font()
            f.setPointSize(24)
            f.setWeight(QtGui.QFont.Bold)
            self.setFont(f)
            self.firstLine = ""
            self.secondLine = ""
        def setFirstLine(self,message):
            '''
            Set The First Line Of Text
            NOTE: Setting The First Line Erases The Second
            '''
            self.firstLine = message
            self.secondLine = ""
            self.showMessage(self.firstLine)
        def setSecondLine(self,message):
            '''
            Set The Second Line Of Text
            '''
            self.secondLine = message
            self.showMessage("%s\n%s"%(self.firstLine,self.secondLine))
        def showMessage(self, message, alignment=QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom, color=QtGui.QColor("#000")):
            '''
            Override of QSplashScreen.showMessage which formats the message to be in the desired position
            '''
            while len(message.split("\n"))!=4: message+="\n"
            return QtGui.QSplashScreen.showMessage(self, message, alignment, color)
    # Since this is the main module, try to change Process Name
    try:
        import setproctitle
        setproctitle.setproctitle(DarkDev.PROCNAME)
    except:
        pass
    # Try To Fix Windows 7 Grouping
    if os.name == 'nt':
        try:
            import ctypes
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('%s.%s'%(DarkDev.APPNAME,DarkDev.VERSION))
        except:
            pass
    # Setup QApplication
    app=QtGui.QApplication(sys.argv)
    app.setApplicationName(DarkDev.APPNAME)
    app.setApplicationVersion(DarkDev.VERSION)
    app.setWindowIcon(QtGui.QIcon(DarkDev.ICON))
    app.setAttribute(QtCore.Qt.AA_DontShowIconsInMenus, False)
    # Create and Show Splash Screen
    im = QtGui.QPixmap('resources/splash.png')
    sp = SplashScreen(im)
    sp.setDisabled(True)
    sp.show()
    app.processEvents()
    # Create DarkDev Instance
    DarkDev(sp)
    # Create Timer So Splash Screen Image Can Load
    t = QtCore.QTimer()
    t.setSingleShot(True)
    t.start(1000)
    # On Timeout Initialize DarkDev and Then Hide the Splash Screen
    t.timeout.connect(lambda *a,**k:DarkDev.INSTANCE.init() or sp.hide())
    # Run DarkDev
    app.exec_()
    # Clean Before Exiting
    DarkDev.clean()
